package org.wesc.boot.service.system.log;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.wesc.boot.dao.domain.QueryRequest;
import org.wesc.boot.dao.entity.SysLog;
import org.wesc.boot.dao.mapper.SysLogMapper;
import org.wesc.boot.dao.service.SysLogService;
import org.wesc.boot.dao.utils.SortUtil;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 系统日志高级服务
 * @author Wesley
 * @date 2021/4/29
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLogAdvanceService {

    private final SysLogMapper sysLogMapper;

    private final SysLogService sysLogService;

    private final ObjectMapper objectMapper;

    /**
     * 查询操作日志分页
     *
     * @param sysLog 日志
     * @param request   QueryRequest
     * @return IPage<SystemLog>
     */
    public IPage<SysLog> findLogs(SysLog sysLog, QueryRequest request) {
        if (StringUtils.isNotBlank(sysLog.getCreateTimeFrom()) &&
                StringUtils.equals(sysLog.getCreateTimeFrom(), sysLog.getCreateTimeTo())) {
            sysLog.setCreateTimeFrom(sysLog.getCreateTimeFrom() + " 00:00:00");
            sysLog.setCreateTimeTo(sysLog.getCreateTimeTo() + " 23:59:59");
        }
        QueryWrapper<SysLog> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(sysLog.getUserName())) {
            queryWrapper.lambda().eq(SysLog::getUserName, sysLog.getUserName().toLowerCase());
        }
        if (StringUtils.isNotBlank(sysLog.getOperation())) {
            queryWrapper.lambda().like(SysLog::getOperation, sysLog.getOperation());
        }
        if (StringUtils.isNotBlank(sysLog.getLocation())) {
            queryWrapper.lambda().like(SysLog::getLocation, sysLog.getLocation());
        }
        if (StringUtils.isNotBlank(sysLog.getCreateTimeFrom()) && StringUtils.isNotBlank(sysLog.getCreateTimeTo())) {
            queryWrapper.lambda()
                    .ge(SysLog::getCreateTime, sysLog.getCreateTimeFrom())
                    .le(SysLog::getCreateTime, sysLog.getCreateTimeTo());
        }

        Page<SysLog> page = new Page<>(request.getPageNum(), request.getPageSize());
        SortUtil.handlePageSort(request, page, "createTime", "desc", true);

        return this.sysLogService.page(page, queryWrapper);
    }

    /**
     * 删除操作日志
     *
     * @param logIds 日志 ID集合
     */
    public void deleteLogs(String[] logIds) {
        List<String> list = Arrays.asList(logIds);
        this.sysLogMapper.deleteBatchIds(list);
    }

    /**
     * 异步保存操作日志
     *
     * @param username   用户名
     * @param point     切点
     * @param method    Method
     * @param ip   ip
     * @param operation 操作内容
     * @param start     开始时间
     */
    @Async("sysLogPoolTaskExecutor")
    public void saveLog(String username, ProceedingJoinPoint point, Method method, String ip, String operation, long start) {
        SysLog sysLog = new SysLog();
        sysLog.setUserName(username);
        sysLog.setIp(ip);
        sysLog.setDuration(new BigDecimal(System.currentTimeMillis() - start));
        sysLog.setOperation(operation);
        String className = point.getTarget().getClass().getName();
        String methodName = method.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        Object[] args = point.getArgs();
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        Object[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            params = handleParams(params, args, Arrays.asList(paramNames));
            sysLog.setParams(params.toString());
        }
        sysLog.setCreateTime(LocalDateTime.now());
        sysLog.setLocation("");
        this.sysLogService.save(sysLog);
    }

    /**
     * 参数处理
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List<Object> paramNames) {
        try {
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Map) {
                    Set set = ((Map) args[i]).keySet();
                    List<Object> list = new ArrayList<>();
                    List<Object> paramList = new ArrayList<>();
                    for (Object key : set) {
                        list.add(((Map) args[i]).get(key));
                        paramList.add(key);
                    }
                    return handleParams(params, list.toArray(), paramList);
                } else {
                    if (args[i] instanceof Serializable) {
                        Class<?> aClass = args[i].getClass();
                        try {
                            aClass.getDeclaredMethod("toString", new Class[]{null});
                            // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ，安全的 writeValueAsString ，否则 走 Object的 toString方法
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                        } catch (NoSuchMethodException e) {
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                        }
                    } else if (args[i] instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) args[i];
                        params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                    } else {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                    }
                }
            }
        } catch (Exception ignore) {
            params.append("参数解析失败");
        }
        return params;
    }

}
